﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace Test
{
    class Region
    {
        private HashSet<Area> areas;
        private static double minDistFactor = 1.0;
        private static double maxDistFactor = 0.4;
        private static double seekDistFactor = 1.0; // min + max < seek * sqrt(2)
        
        public Region(int size, double dist)
        {
            Random random = new Random();
            areas = new HashSet<Area>();

            Area start = new Area();
            start.Position = new Point(600, 400);
            start.Type = AreaType.Start;
            this.addArea(start);
            
            Area prev = start;
            Area next = null;
            Area tmp = null;
            int townDist = random.Next(1, size/4);
            for (int i = 0; i < townDist; i++)
            {
                next = new Area();
                next.Level = prev.Level + 1;
                next.Type = AreaType.PreTown;
                do
                {
                    double theta = random.NextDouble() * Math.PI * 2;
                    double r = dist * minDistFactor + random.NextDouble() * dist * maxDistFactor;
                    next.Position = new Point(prev.Position.X + Math.Cos(theta) * r, prev.Position.Y + Math.Sin(theta) * r);
                    tmp = findArea(next.Position, dist*seekDistFactor, next);
                } while (tmp != null && tmp != next);

                prev.setNext(next, true);
                addArea(next);
                prev = next;
                next = null;
            }
            
            Area town = prev;
            town.Type = AreaType.Town;
            town.Level--;
            
            prev = town;
            next = null;
            tmp = null;
            int maxlevel = 0;
            while (this.size() < size)
            {
                if (prev == null)
                {
                    List<Area> areasList = new List<Area>();
                    foreach (Area a in areas)
                    {
                        if (a.Type != AreaType.PreTown && a.Type != AreaType.Start) areasList.Add(a);
                    }
                    prev = areasList.ElementAt(random.Next(areasList.Count));
                }
                next = new Area();
                next.Level = prev.Level + 1;
                next.Type = AreaType.PostTown;
                Boolean ok = false;
                int k = 0;
                do
                {
                    double theta = random.NextDouble() * Math.PI * 2;
                    double r = dist * minDistFactor + random.NextDouble() * dist * maxDistFactor;
                    next.Position = new Point(prev.Position.X + Math.Cos(theta) * r, prev.Position.Y + Math.Sin(theta) * r);
                    tmp = findArea(next.Position, dist*seekDistFactor, next);
                    if (tmp == null) ok = true;
                    else if (tmp.Level == next.Level && prev.distance(tmp.Position) < dist * (minDistFactor + maxDistFactor))
                    {
                        next = tmp;
                        ok = true;
                    }
                    else k++;
                } while (!ok && k < 10);

                if (k != 10)
                {
                    prev.setNext(next, true);
                    addArea(next);
                    maxlevel = (next.Level > maxlevel) ? next.Level : maxlevel;
                }
                prev = null;
                next = null;
            }

            foreach (Area a in areas)
            {
                if (a.Level == maxlevel) a.Type = AreaType.End;
            }


        }

        public int size()
        {
            return areas.Count();
        }

        public HashSet<Area> Areas
        {
            get { return areas; }
        }

        public void addArea(Area area)
        {
            areas.Add(area);
        }

        public Area findArea(Point position, double distance, Area excludes)
        {
            foreach (Area a in areas)
            {
                if (a.distance(position) < distance && a != excludes)
                {
                    return a;
                }
            }
            return null;
        }
    }
}
